//License: Artistic License v2.0, http://opensource.org/licenses/Artistic-2.0

package main

import (
	"encoding/csv"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
)

var ipLocationTable [][]string //table of (numerical) ip addr and location
var ipSearchRange []float64    //numerical ip addr range used for searching

//GetLocation searches the ip addr string of format w.x.y.z in the ip location table
//that is constructed in SetIPLocationTable beforehand.
//and returns city, area/state, ISO code as string according to the raw data CSV file.
//User is responsible to pick whichever column / field contains the relevant location
//info based on the data set from the data provider. Not all data file is the same.
//If raw data file does not have the specific data point you want (e.g. city, area),
//then please give it a -1 as in the example below.
//
//For example:
//If using raw data file DB4 from ip2location.com, then you can have country, area/state,
//and city.  The data looks like this:
//"201672192","201672223","US","UNITED STATES","TEXAS","DALLAS","".
//Calling GetIPLocation("w.x.y.z", 5, 4, 2) will return "DALLAS, TEXAS, US".
//If using raw data from software77.net, the data file only has country.
//The data looks like this:
//"49807360","50331647","","1293667200","SE","SWE","Sweden".
//Calling GetIPLocation("w.x.y.z", 4, -1, -1) will return "SE" or
//GetIPLocation("w.x.y.z", 6, -1, -1) will return "Sweden".
func GetIPLocation(str string, city, area, country int) string {
	var ip float64
	if len(ipSearchRange) == 0 || len(ipLocationTable) == 0 {
		return ""
	}
	s := strings.Split(str, ":") //ip may include port # that we need to drop
	t := strings.Split(s[0], ".")
	i, err := strconv.ParseFloat(t[3], 64)
	if err != nil {
		return ""
	}
	ip = i
	i, err = strconv.ParseFloat(t[2], 64)
	if err != nil {
		return ""
	}
	ip = ip + (i * 256)
	i, err = strconv.ParseFloat(t[1], 64)
	if err != nil {
		return ""
	}
	ip = ip + (i * 256 * 256)
	i, err = strconv.ParseFloat(t[0], 64)
	if err != nil {
		return ""
	}
	ip = ip + (i * 256 * 256 * 256)
	idx := sort.SearchFloat64s(ipSearchRange, ip)
	var locationStr string
	columnCount := len(ipLocationTable[0])
	if city >= 0 && city < columnCount {
		locationStr = ipLocationTable[idx][city]
	}
	if area >= 0 && area < columnCount {
		locationStr = locationStr + ", " + ipLocationTable[idx][area]
	}
	if country >= 0 && country < columnCount {
		locationStr = locationStr + ", " + ipLocationTable[idx][country]
	}
	return locationStr
}

//SetLocationTable loads the IP location raw data from CSV file into memory
//for the consumption of GetLocation.  It reads CSV file with ALL of the header removed.
//The raw data can come from http://Software77.net, http://ip2location.com, et cetera.
//Different data provider and different data set will contain different things.
//Some contain only country location.  Others contain other details, e.g city, state.
//(Please note data provider's license term on the data they provide.)
//
//The raw data will contain "IP From" and "IP To" columns. ipTo is the column number
//of the "IP To" column (zero-based index).  Usually it is the second column, i.e.
//ipTo == 1, but it is user-defined to accommodate weird raw data file.
//Warning: Depending on the raw data set, the CSV file may be large and, accordingly, its
//memory consumption.  You can reduce the size by removing unnecessary columns from the
//CSV file, but do not forget to set your calling parameters in GetIPLocation accordingly.
func SetIPLocationTable(fname string, ipTo int) (err error) {
	log.Println("Setting up geolocation detection")
	if ipTo < 0 {
		return nil //TODO: register a new err
	}
	f, err := os.OpenFile(fname, os.O_RDONLY, 0600)
	if err != nil {
		return err
	}
	defer f.Close()
	csvData := csv.NewReader(f)
	ipLocationTable, err = csvData.ReadAll()
	if err != nil {
		return err
	}
	ipSearchRange = make([]float64, 0) //must be reset to enable hot swap / live-update
	for i := 0; i < len(ipLocationTable); i++ {
		ip, err := strconv.ParseFloat(ipLocationTable[i][ipTo], 64)
		if err != nil {
			return err
		}
		ipSearchRange = append(ipSearchRange, ip)
	}
	return nil
}

//Copyright: Slamet Hendry
